import { Game } from "../../game/Game";
import { MapUtil } from "../../game/lib/kgame/util/MapUtil";
import { KProto } from "../../game/proto/KProto";
import { Global } from "../Global";
import { LocalConn } from "./LocalConn";

/**
 * 通信连接
 */
export class Conn {

    private static _localConn: LocalConn;
    private static _cbMap = new Map<KProtoId, { cb: (data: any, protoId: KProtoId) => any, caller: any }>();
    private static _asyncMap = new Map<KProtoId, ((data: any) => any)[]>();
    private static _ws: WebSocket;

    static showLog = false;

    /**
     * 本地连接
     * @param localConn 
     */
    static connLocal(game: Game, onConn: () => void) {
        this._localConn = new LocalConn(game);
        onConn();
    }

    /** 断开连接 */
    static disConn() {
        if (this._localConn) {
            this._localConn = null;
        } else {
            this._ws.close();
        }
    }

    /**
     * 远程连接
     */
    static connRemote(url: string, onSucc: () => void, onFail: () => void, onDisConn: () => void) {
        try {
            const ws = this._ws = new WebSocket(url);
            ws.binaryType = "arraybuffer";
            ws.onerror = e => onFail();
            ws.onopen = e => {
                ws.onclose = e => {
                    onDisConn();
                }
                ws.onmessage = e => {
                    //协议通信
                    const protoId = KProto.decodeId(new Uint8Array(e.data));
                    const data = KProto.decodeToc(protoId as any);
                    this.onProto(protoId as any, data);
                }
                onSucc();
            }
        } catch { onFail(); }
    }

    /**
     * 监听协议
     * @param protoId 
     * @param cb 
     * @param caller 
     */
    static on<ID extends KProtoTocNeed>(protoId: ID, cb: (data: KProtoToc[ID], protoId: KProtoId) => any, caller?: any): void
    static on<ID extends KProtoTocNever>(protoId: ID, cb: (data: void, protoId: KProtoId) => any, caller?: any): void
    static on(protoId: KProtoId, cb: (data: any, protoId: KProtoId) => any, caller?: any): void {
        this._cbMap.set(protoId, { cb, caller });
    }

    /**
     * 接收到协议
     * @param protoId 
     * @param data 
     */
    static onProto(protoId: KProtoId, data?: any): void {
        if (this.showLog) {
            Global.logger.log(`接收:${protoId} <<< ${stringifyData(data)}`);
        }

        this._asyncMap?.get(protoId)?.pop().call(this, data);
        const cb = this._cbMap.get(protoId);
        if (cb) {
            cb.cb.call(cb.caller, data, protoId);
        }
    }

    /**
     * 发送协议
     * @param protoId 
     * @param data 
     */
    static send<ID extends KProtoTosNeed>(protoId: ID, data: KProtoTos[ID]): void;
    static send<ID extends KProtoTosNever>(protoId: ID): void;
    static send(protoId: KProtoId, data?: any): void {
        if (this.showLog) {
            Global.logger.log(`发送:${protoId} >>> ${stringifyData(data)}`);
        }

        if (this._localConn) {
            this._localConn.onProto(protoId, data);
        } else {
            KProto.encodeId(protoId);
            this._ws.send(KProto.encodeTos(protoId as any, data));
        }
    }

    /**
     * 发送协议 返回Promise形式
     * @param protoId 
     * @param data 
     */
    static sendAsync<ID extends KProtoTosNeed & KProtoTocNeed>(protoId: ID, data: KProtoTos[ID]): Promise<KProtoToc[ID]>
    static sendAsync<ID extends KProtoTosNeed & KProtoTocNever>(protoId: ID, data: KProtoTos[ID]): Promise<void>
    static sendAsync<ID extends KProtoTosNever & KProtoTocNeed>(protoId: ID): Promise<KProtoToc[ID]>
    static sendAsync<ID extends KProtoTosNever & KProtoTocNever>(protoId: ID): Promise<void>
    static sendAsync(protoId: KProtoId, data?: any): Promise<any> {
        return new Promise(res => {
            MapUtil.addToArrMap(this._asyncMap, protoId, data => res(data));
            this.send(protoId as never, data as never);
        });
    }
}

function stringifyData(data: any) {
    if (data) {
        try {
            return JSON.stringify(data);
        } catch { }
    }
    return "";
}